/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

import { HvigorLogger } from '../../../log/hvigor-log.js';
import { cpuUsage } from '../../../util/cpu.js';
import { PoolConstant } from '../constant/constant.js';
import { generateScheduleLog } from '../log-collection/log.js';
import { LogServiceImpl } from '../log-collection/log-service-impl.js';
import { ITCB } from '../model/itcb.js';
import { Work } from '../model/work.js';
import { ClusterPriorityReadyQueue } from '../ready-queue/cluster-priority-ready-queue.js';
import { ReadyQueue } from '../ready-queue/ready-queue.js';
import { TcbStore } from '../store/tcb-store.js';
import { WorkerManager } from '../worker-manager/worker-manager.js';

import { Dispatcher } from './dispatcher.js';

const log = HvigorLogger.getLogger('workerDispatcher');

/**
 * 每次遍历worker进行分发的优先级分发器
 *
 * @since 2022/8/24
 */
export class LoopPriorityDispatcher implements Dispatcher {
  private failedAttempts = 0;

  dispatch(logService: LogServiceImpl, readyQueue: ReadyQueue, workerManager: WorkerManager, workerId?: number): boolean {
    if (readyQueue.empty()) {
      return false;
    }
    if (cpuUsage >= PoolConstant.CPU_LIMIT && this.failedAttempts < PoolConstant.MAX_FAIL_ATTEMPTS) {
      this.failedAttempts++;
      setTimeout(() => {
        this.dispatch(logService, readyQueue, workerManager, workerId);
      }, PoolConstant.FAIL_DISPATCH_INTERVAL);
      return false;
    }
    this.failedAttempts = 0;
    const clusterReadyQueue: ClusterPriorityReadyQueue = readyQueue as ClusterPriorityReadyQueue;
    if (workerId !== undefined) {
      return this.dispatchWithGivenWorker(logService, clusterReadyQueue, workerManager, workerId);
    }
    for (let workerId = workerManager.getMaxPoolNum(); workerId >= 0; workerId--) {
      if (this.dispatchWithGivenWorker(logService, clusterReadyQueue, workerManager, workerId)) {
        return true;
      }
    }
    return false;
  }

  private dispatchWithGivenWorker(
    logService: LogServiceImpl,
    clusterReadyQueue: ClusterPriorityReadyQueue,
    workerManager: WorkerManager,
    workerId: number,
  ): boolean {
    // busy worker直接拒绝，不会尝试取出work
    if (workerManager.isBusyWorker(workerId)) {
      log.debug(`A work dispatched to worker[${workerId}] failed because of worker busy.`);
      return false;
    }
    const work: Work | undefined = clusterReadyQueue.getFirstWaitingWork(workerId);
    if (!work) {
      log.debug(`A work dispatched to worker[${workerId}] failed because unable to get work from ready queue.`);
      return false;
    }
    const task = TcbStore.getTCB(work.getId());
    const taskName = task?.getTaskName();

    if (!workerManager.isIdleWorker(workerId) && !workerManager.createWorker(workerId, work.getPreludeDeps())) {
      log.debug(`${taskName} work[${work.getId()}] dispatched to worker[${workerId}] failed: create worker failed.`);
      clusterReadyQueue.pushWork(work, workerId);
      return false;
    }

    if (workerManager.doWork(work, task!.getCallback(), task!.getCallbackInput(), workerId)) {
      log.debug(`${taskName} work[${work.getId()}] has been dispatched to worker[${workerId}].`);
      if (!(TcbStore.getTCB(work.getId()) as ITCB).isWarmUp()) {
        logService.addLog(generateScheduleLog(`${Date.now()}`, work.getId(), workerId));
      }
      return true;
    }
    log.debug(`${taskName} work[${work.getId()}] dispatched to worker[${workerId}] failed: doWork failed.`);
    clusterReadyQueue.pushWork(work, workerId);
    return false;
  }
}
